package ar.com.fiuba.ElectronicCircuitAnalyst.domain.impl;

import java.util.ArrayList;
import java.util.List;

import ar.com.fiuba.ElectronicCircuitAnalyst.domain.api.Circuit;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.api.CircuitElement;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.api.Connection;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.api.Lead;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.common.Specification;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.exceptions.CircuitElementNotFoundException;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.exceptions.CircuitInputValuesException;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.exceptions.InvalidCircuitElementException;
import ar.com.fiuba.ElectronicCircuitAnalyst.domain.exceptions.SimulationException;

public class CircuitImpl extends CircuitElementImpl implements Circuit {

	private List<CircuitElement> circuitElements;
	private List<Connection> connections;
	private Specification<Circuit> validCircuitSpec;

	/**
	 * Factory method to get an empty circuit. This is for repository use only.
	 * 
	 * @param name
	 *            the name for the desired circuit.
	 * @return an instance of an empty circuit with the given name.
	 */
	public static CircuitImpl getEmptyCircuit(String name) {
		return new CircuitImpl(name);
	}

	private CircuitImpl(String name) {
		this.name = name;
	}

	public CircuitImpl(String name, List<Lead> inputs, List<Lead> outputs, List<CircuitElement> circuitElements,
			Specification<Circuit> validCircuitSpec) {
		this.name = name;
		this.inputLeads = inputs;
		this.outputLeads = outputs;
		this.circuitElements = circuitElements;
		this.validCircuitSpec = validCircuitSpec;
	}

	public void simulate() throws SimulationException {
		for (CircuitElement circuitElement : this.circuitElements) {
			circuitElement.simulate();
		}
		for (Connection connection : this.connections) {
			connection.connect();
		}
	}

	public void setInputValues(List<Boolean> values) throws CircuitInputValuesException {
		if (values.size() != this.inputLeads.size()) {
			throw new CircuitInputValuesException("Input values count differ from circuit inputs count");
		}
		Lead inputLead;
		for (int i = 0; i < values.size(); i++) {
			inputLead = this.inputLeads.get(i);
			inputLead.setValue(values.get(i));
			List<Connection> leadConnections = inputLead.getConnections();
			for (Connection leadConnection : leadConnections) {
				leadConnection.connect();
			}
		}
	}

	public List<Boolean> getOutputValues() {
		List<Boolean> values = new ArrayList<Boolean>();
		for (int i = 0; i < this.outputLeads.size(); i++) {
			values.add(this.outputLeads.get(i).getValue());
		}
		return values;
	}

	public List<CircuitElement> getCircuitElements() {
		return this.circuitElements;
	}

	private void validateCircuit() throws InvalidCircuitElementException {
		if (!this.validCircuitSpec.isSatisfiedBy(this))
			throw new InvalidCircuitElementException("Circuit doesn't satisfy specification.");
	}

	public CircuitElement getCircuitElementByName(String name) throws CircuitElementNotFoundException {

		for (CircuitElement circuitElement : circuitElements) {
			if (circuitElement.getName().equals(name)) {
				return circuitElement;
			}
		}

		throw new CircuitElementNotFoundException("Circuit Element with the name \"" + name + "\" not found.");
	}

	public void setConnections(List<Connection> connections) throws InvalidCircuitElementException {
		this.connections = connections;
		this.validateCircuit();
	}

}
